<template>
    <div
        class="progress-bar"
        ref="progressBar"
        v-resize:[debounceOptions].debounce="handleResize"
        @click.prevent.stop="progressClick"
    >
        <div
            class="progress"
            ref="progress"
            :style="{ width: `${progressOffsetWidth}px` }"
        ></div>
        <div
            class="buffered"
            ref="buffered"
            :style="{ width: `${bufferedOffsetWidth}px` }"
        ></div>
        <div
            :class="handleCls"
            @mousedown="onMouseDown"
            :style="{ transform: `translateX(${progressbarTranslateX}px)` }"
        >
            <img
                src="./../../assets/images/loading.gif"
                class="progress-waiting"
                v-if="waiting"
            />
            <div class="progress-btn" v-else></div>
        </div>
        <div class="virtual-bar" ref="virtualBar"></div>
    </div>
</template>

<script>
import { debounce } from "@/utils/dom";

export default {
    name: "progressBar",
    data() {
        this.debounceOptions = { wait: 50 };
        this.mouse = {}; // 记录鼠标位置，是否按下等信息
        return {
            bufferedOffsetWidth: 0,
            progressOffsetWidth: 0,
            progressbarTranslateX: 0,
        };
    },
    props: {
        percent: {
            type: Number,
            default: 0,
        },
        bufferedPercent: {
            type: Number,
            default: 0,
        },
        size: {
            type: String,
            default: "default",
            validator(value) {
                return ["default", "small"].includes(value);
            },
        },
        waiting: {
            type: Boolean,
            default: false,
        },
    },
    mounted() {
        this.changeProgressbarWidth(this.percent);

        const virtualBar = this.$refs.virtualBar;
        virtualBar.addEventListener(
            "mousemove",
            debounce((e) => {
                e.stopPropagation();
                const progressBar = this.$refs.progressBar;
                let pageX = e.pageX;
                let diff = pageX - progressBar.getBoundingClientRect().left;
                let percent = (diff / progressBar.clientWidth).toFixed(2);
                this.$emit("virtualBarMove", { pageX, percent });
            }, 200)
        );
        virtualBar.addEventListener(
            "mouseleave",
            debounce(() => {
                this.$emit("virtualBarLeave");
            }, 200)
        );
    },
    computed: {
        handleCls() {
            return this.size === "small" ? "handle small" : "handle";
        },
    },
    watch: {
        percent(newPercent) {
            if (newPercent >= 0 && !this.mouse.isDown) {
                this.changeProgressbarWidth(newPercent);
            }
        },
        bufferedPercent(newPercent) {
            this.changeBufferedWidth(newPercent);
        },
    },
    methods: {
        handleResize() {
            this.changeBufferedWidth(this.bufferedPercent);
            this.changeProgressbarWidth(this.percent);
        },
        onMouseDown(e) {
            e.preventDefault();
            this.mouse.startX = e.pageX;
            this.mouse.isDown = true;
            this.mouse.left = this.$refs.progress.clientWidth;
            document.onmousemove = (e) => {
                e.preventDefault();
                if (!this.mouse.isDown) return;
                const progressBar = this.$refs.progressBar;
                const progress = this.$refs.progress;
                const progressBarWidth = progressBar.getBoundingClientRect()
                    .width;
                this.mouse.moveX = e.pageX;
                let diffX =
                    this.mouse.moveX - this.mouse.startX + this.mouse.left;
                if (diffX < 0) {
                    diffX = 0;
                }
                if (diffX > progressBarWidth) {
                    diffX = progressBarWidth;
                }
                this.progressOffsetWidth = diffX;
                this.progressbarTranslateX = diffX;
                this.$emit("percentChanging", this.calcPercent());
            };
            document.onmouseup = (e) => {
                if (!this.mouse.isDown) return;
                this.mouse.isDown = false;
                document.onmousemove = null;
                this.$emit("percentChanged", this.calcPercent());
            };
        },
        progressClick(e) {
            const rect = this.$refs.progressBar.getBoundingClientRect();
            const offsetWidth = e.pageX - rect.left;
            this.progressOffsetWidth = offsetWidth;
            this.progressbarTranslateX = offsetWidth;
            this.$emit("percentChanged", this.calcPercent());
        },
        calcPercent() {
            return (
                this.progressOffsetWidth / this.$refs.progressBar.clientWidth
            ).toFixed(2);
        },
        changeProgressbarWidth(percent) {
            const barWidth = this.$refs.progressBar.getBoundingClientRect()
                .width;
            const offsetWidth = percent * barWidth;
            this.progressOffsetWidth = offsetWidth;
            this.progressbarTranslateX = offsetWidth;
        },
        changeBufferedWidth(percent) {
            const barWidth = this.$refs.progressBar.getBoundingClientRect()
                .width;
            const offsetWidth = percent * barWidth;
            this.bufferedOffsetWidth = offsetWidth;
        },
    },
};
</script>

<style lang="less" scoped>
.progress-bar {
    position: relative;
    width: 100%;
    height: 4px;
    border-radius: 2px;
    background: rgba(123, 123, 123, 0.3);
    cursor: pointer;
    .virtual-bar {
        position: absolute;
        left: 0;
        top: -4px;
        right: 0;
        bottom: -4px;
        z-index: 2;
    }
    .progress {
        position: absolute;
        left: 0;
        top: 0;
        z-index: 2;
        height: 100%;
        background: @primary-color;
        border-radius: 2px;
    }
    .buffered {
        position: absolute;
        left: 0;
        top: 0;
        z-index: 1;
        height: 100%;
        background: rgba(123, 123, 123, 0.5);
        transition: all 0.1s ease-out;
    }
    .handle {
        position: absolute;
        left: -10px;
        top: 50%;
        margin-top: -8px;
        z-index: 3;
        width: 20px;
        height: 16px;
        background: #fff;
        border-radius: 8px;
        border: 1px solid #ddd;
        cursor: pointer;
        box-sizing: border-box;
        &.small {
            left: -7px;
            margin-top: -7px;
            width: 14px;
            height: 14px;
            border-radius: 7px;
            .progress-btn {
                top: 3px;
                left: 3px;
            }
        }
        &:hover {
            box-shadow: 0px 0px 3px 2px rgba(0, 0, 0, 0.1);
        }
        .progress-btn {
            position: relative;
            top: 4px;
            left: 6px;
            z-index: 1;
            box-sizing: border-box;
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: @primary-color;
        }
        .progress-waiting {
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            width: 10px;
            height: 10px;
            z-index: 2;
        }
    }
}
</style>
